'''ffpipe
使用pipe与ffmpeg通讯，读取或者写入视频帧

用法：
    获得路径为file的视频流信息infos
        infos = get_params(file)

    从头读取路径为file的视频的每一帧数据
        incoming = frame_reader(file)

    将图片imgs写入到为路径为file的视频，参数为opts
        output = frame_writer(file, opts) #创建生成器
        next(output) #让程序流推进到yield
        output.send(imgs) #写入图片帧
'''
import json
import subprocess as sp

from PIL import Image

FFPROBE = 'ffprobe.exe'
FFMPEG = 'ffmpeg.exe'


def get_params(filepath):
    '''
    获取目标文件的视频流信息
    get_params(filepath)->info

    参数列表：
        filepath (str) 视频路径

    返回值：
        info (dict) 视频流信息
    '''
    command = [FFPROBE, '-print_format', 'json',
               '-show_streams', '-i', str(filepath)]
    pipe = sp.Popen(command, stdout=sp.PIPE, stderr=sp.PIPE)
    infos = pipe.stdout.read()
    jsondat = json.loads(str(infos, encoding='utf8'))
    for stream in jsondat['streams']:
        width = stream.get('width')
        height = stream.get('height')
        frame_rate = stream.get('avg_frame_rate')
        if (width and height and frame_rate) != None:
            ret = stream
            break
    return ret


def null_handler(stderr_content):
    '''
    空回调处理函数
    null_handler(stderr_content)->None

    参数列表：
        stderr_content (bytes) 二进制字符串

    返回值：
        None
    '''
    pass


def frame_reader(filepath, stderr_handler=null_handler):
    '''
    帧读取生成器
    frame_reader(filepath, stderr_handler)->generator->PIL.Image.Image()

    参数列表：
        filepath (str) 视频路径
        stderr_handler (function(arg1)) 标准错误输出处理函数

    返回值：
        generator 返回PIL.Image.Image()对象
    '''
    params = get_params(filepath)
    bytes_per_frame = params['width'] * params['height'] * 3
    command = [FFMPEG,
               '-i', str(filepath),
               '-f', 'image2pipe',
               '-pix_fmt', 'rgb24',
               '-vcodec', 'rawvideo', '-']
    pipe = sp.Popen(command, stdout=sp.PIPE,
                    stderr=sp.STDOUT, bufsize=bytes_per_frame * 3)
    while True:
        try:
            img = Image.frombytes(
                'RGB', (params['width'], params['height']), pipe.stdout.read(bytes_per_frame))
            pipe.stdout.flush()
            # stderr_handler(pipe.stderr.read())
            # pipe.stderr.flush()
        except ValueError as inadequate_data:
            # 解码结束后缓冲区无数据，但数据不足时Image.frombytes抛出ValueError。
            print(inadequate_data)
            pipe.terminate()
            break
        else:
            yield img
    return StopIteration


def frame_writer(filepath, opts, stderr_handler=null_handler):
    '''
    帧写入生成器
    frame_writer(filepath, opts, stderr_handler)->generator(PIL.Image.Image())->bool

    参数列表：
        filepath (str) 视频路径
        opts (dict) 视频参数
        stderr_handler (function(arg1)) 标准错误输出处理函数

    返回值：
        generator 接受PIL.Image.Image()对象，返回bool表示是否写入
    '''
    assert ('width' in opts) and (
        'height' in opts) and ('avg_frame_rate' in opts)
    size = (str(opts.get('width')), str(opts.get('height')))
    bytes_per_frame = opts['width'] * opts['height'] * 3
    frame_rate = str(opts.get('avg_frame_rate'))
    command = [FFMPEG,
               '-y',  # (optional) overwrite output file if it exists
               '-f', 'rawvideo',
               '-vcodec', 'rawvideo',
               '-s', 'x'.join(size),  # size of one frame
               '-pix_fmt', 'rgb24',
               '-r', frame_rate,  # frames per second
               '-i', '-',  # The imput comes from a pipe
               '-an',  # Tells FFMPEG not to expect any audio
               '-vcodec', 'mpeg4',
               str(filepath)]

    pipe = sp.Popen(command, stdin=sp.PIPE, stderr=sp.STDOUT,
                    bufsize=bytes_per_frame * 3)
    while True:
        image = yield True
        if not isinstance(image, Image.Image):
            pipe.terminate()
            return False
        pipe.stdin.write(image.tobytes())
        # stderr_handler(pipe.stderr.read())
        # pipe.stderr.flush()


'''放弃
#ffmpeg -i input.mp4 -acodec copy -vn -f adts -strict -2 -
def audio_reader(filepath, stderr_handler=null_handler):
    command=[FFMPEG,'-i',str(filepath),'-acodec','copy','-vn','-f','adts','-']
    pipe = sp.Popen(command, stdout=sp.PIPE,
                    stderr=sp.PIPE)
    while True:
        yield pipe.stdout.read(2**16)
        stderr_handler(pipe.stderr.read())
        pipe.stderr.flush()
'''


def avmixer(audiopath, videopath, outputpath):
    '''
    音频视频混合
    avmixer(audiopath,videopath,outputpath)->retcode

    参数列表：
        audiopath (str) 音频文件路径
        videopath (str) 视频文件路径
        outputpath (str) 输出文件路径

    返回值：
        retcode ffmpeg返回值
    '''
    command = [FFMPEG, '-i', audiopath, '-i', videopath,
               '-acodec', 'copy', '-vcodec', 'copy', outputpath]
    return sp.call(command)
